/*
 * Copyright (c) 2016 Zibin Zheng <znbin@qq.com>
 * All rights reserved
 */

#include "multi_button.h"
#include <stdint.h>

#define EVENT_CB(ev)   if(handle->cb[ev])handle->cb[ev]((Button*)handle)

//button handle list head.
static struct Button* head_handle = NULL;

/**
  * @brief  Initializes the button struct handle.
  * @param  handle: the button handle strcut.
  * @param  pin_level: read the HAL GPIO of the connet button level.
  * @param  active_level: pressed GPIO level.
  * @param  button_id: the button id.
  * @retval None
  */
void button_init(struct Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id)
{
	memset(handle, 0, sizeof(struct Button));
	handle->event = (uint8_t)NONE_PRESS;
	handle->hal_button_Level = pin_level;
	handle->button_level = handle->hal_button_Level(button_id);
	handle->active_level = active_level;
	handle->button_id = button_id;
	handle->short_press_ticks = SHORT_TICKS;
	handle->long_press_ticks = LONG_TICKS;
	handle->debounce_ticks = DEBOUNCE_TICKS;
}

/**
  * @brief  Set the button short & long ticks.
  * @param  handle: the button handle strcut.
  * @param  short_ticks:set short ticks, if short_ticks = 0 , ignore
  * @param  long_ticks: set long ticks, if long_ticks = 0 , ignore.
  * @retval None
  */
void set_button_ticks(struct Button* handle, uint16_t short_ticks, uint16_t long_ticks)
{
	if( short_ticks > 0 )
		handle->short_press_ticks = short_ticks;
	
	if( long_ticks > 0 )
		handle->long_press_ticks = long_ticks;
}

/**
  * @brief  Attach the button event callback function.
  * @param  handle: the button handle strcut.
  * @param  event: trigger event type.
  * @param  cb: callback function.
  * @retval None
  */
void button_attach(struct Button* handle, PressEvent event, BtnCallback cb)
{
	handle->cb[event] = cb;
}

/**
  * @brief  Inquire the button event happen.
  * @param  handle: the button handle strcut.
  * @retval button event.
  */
PressEvent get_button_event(struct Button* handle)
{
	return (PressEvent)(handle->event);
}

/**
  * @brief  Get button level for signal button and combo button.
  * @param  handle: the button handle strcut.
  * @retval button level
  */
uint8_t get_button_level(struct Button * handle)
{
	uint8_t button_level = 0;
	uint8_t combination_num = 0;

	button_level = handle->hal_button_Level(handle->button_id);

	while (handle->combination[combination_num] != NULL) 
	{
		Button *handle = handle->combination[combination_num];
		button_level &= handle->hal_button_Level(handle->button_id);
		
		if(button_level == 0)
			break;
	}	

	return button_level;

}

/**
  * @brief  Button driver core function, driver state machine.
  * @param  handle: the button handle strcut.
  * @retval None
  */
void button_handler(struct Button* handle)
{
	uint8_t read_gpio_level = get_button_level(handle);

	//ticks counter working.
	if((handle->state) > 0) handle->ticks++;

	/*------------button debounce handle---------------*/
	if(read_gpio_level != handle->button_level) { //not equal to prev one
		//continue read 3 times same new level change
		if(++(handle->debounce_cnt) >= handle->debounce_ticks) {
			handle->button_level = read_gpio_level;
			handle->debounce_cnt = 0;
		}
	} else { //leved not change ,counter reset.
		handle->debounce_cnt = 0;
	}

	/*-----------------State machine-------------------*/
	switch (handle->state) {
		case BUTTON_RELEASE:
			if(handle->button_level == handle->active_level) {	//start press down
				handle->event = (uint8_t)PRESS_DOWN;
				EVENT_CB(PRESS_DOWN);
				handle->ticks = 0;
				handle->repeat = 1;
				handle->state = BUTTON_PRESS;
			} else {
				handle->event = (uint8_t)NONE_PRESS;
			}
			break;

		case BUTTON_PRESS:
			if(handle->button_level != handle->active_level) { //released press up
				uint8_t combination_num = 0;
				uint8_t combination_pressed = 0;

				handle->event = (uint8_t)PRESS_UP;
				EVENT_CB(PRESS_UP);
				handle->ticks = 0;
				handle->state = BUTTON_CLICK_RELEASE;
					
			} else if(handle->ticks > handle->long_press_ticks) {
				handle->event = (uint8_t)LONG_PRESS_START;
				EVENT_CB(LONG_PRESS_START);
				handle->state = BUTTON_LONG_PRESS;
			}
			break;

		case BUTTON_CLICK_RELEASE:
			if(handle->button_level == handle->active_level) { //press down again
				handle->event = (uint8_t)PRESS_DOWN;
				EVENT_CB(PRESS_DOWN);
				handle->repeat++;
				EVENT_CB(PRESS_REPEAT); // repeat hit
				handle->ticks = 0;
				handle->state = BUTTON_CLICK_PRESS;
			} else if(handle->ticks > handle->short_press_ticks) { //released timeout
				if(handle->repeat == 1) {
					handle->event = (uint8_t)SINGLE_CLICK;
					EVENT_CB(SINGLE_CLICK);
				} else if(handle->repeat == 2) {
					handle->event = (uint8_t)DOUBLE_CLICK;
					EVENT_CB(DOUBLE_CLICK); // repeat hit
				}
				handle->state = BUTTON_RELEASE;
			}
			break;

		case BUTTON_CLICK_PRESS:
			if(handle->button_level != handle->active_level) { //released press up
				handle->event = (uint8_t)PRESS_UP;
				EVENT_CB(PRESS_UP);
				if(handle->ticks < handle->short_press_ticks) {
					handle->ticks = 0;
					handle->state = BUTTON_CLICK_RELEASE; //repeat press
				} else {
					handle->state = BUTTON_RELEASE;
				}
			}else if(handle->ticks > handle->short_press_ticks){ // long press up
				handle->state = BUTTON_RELEASE;
			}
			break;

		case BUTTON_LONG_PRESS:
			if(handle->button_level == handle->active_level) {
				//continue hold trigger
				handle->event = (uint8_t)LONG_PRESS_HOLD;
				EVENT_CB(LONG_PRESS_HOLD);

			} else { //releasd
				handle->event = (uint8_t)PRESS_UP;
				EVENT_CB(PRESS_UP);
				handle->state = BUTTON_RELEASE; //reset
			}
			break;
		default:
			handle->state = BUTTON_RELEASE; //reset
			break;
	}
}

/**
  * @brief  Start the button work, add the handle into work list.
  * @param  handle: target handle strcut.
  * @retval 0: succeed. -1: already exist.
  */
int button_start(struct Button* handle)
{
	struct Button* target = head_handle;
	while(target) {
		if(target == handle) return -1;	//already exist.
		target = target->next;
	}
	handle->next = head_handle;
	head_handle = handle;
	return 0;
}

/**
  * @brief  Stop the button work, remove the handle off work list.
  * @param  handle: target handle strcut.
  * @retval None
  */
void button_stop(struct Button* handle)
{
	struct Button** curr;
	for(curr = &head_handle; *curr; ) {
		struct Button* entry = *curr;
		if (entry == handle) {
			*curr = entry->next;
//			free(entry);
			return;//glacier add 2021-8-18
		} else
			curr = &entry->next;
	}
}

/**
  * @brief  background ticks, timer repeat invoking interval 5ms.
  * @param  None.
  * @retval None
  */
void button_ticks()
{
	struct Button* target;
	for(target=head_handle; target; target=target->next) {
		button_handler(target);
	}
}


int button_add_combination(struct Button* handle, struct Button** handle_list, uint8_t num)
{
	uint8_t i = 0;

	if (num > MAX_COMBINATION)
	{
		return -1;
	}
	
	for ( i = 0; i < num; i++ )
	{
		handle->combination[i] = handle_list[i];
	}
	
	return 0;
}